package 加密与安全;
/*
我们知道，摘要算法用来确保数据没有被篡改，非对称加密算法可以对数据进行加解密，签名算法可以确保数据完整性和抗否认性，把这些算法集合到一起，
并搞一套完善的标准，这就是数字证书。

因此，数字证书就是集合了多种密码学算法，用于实现数据加解密、身份认证、签名等多种功能的一种安全标准。

数字证书，包含公钥、主体信息、扩展部分等诸多内容，最后使用签名算法，用私钥对这些信息进行签名。在双方通信过程中，通过数字证书的传递，可以理解为识
别对方主体信息、并交换了公钥，及对其中的签名进行认证（判断可信度）。

数字证书可以防止中间人攻击，因为它采用链式签名认证，即通过根证书（Root CA）去签名下一级证书，这样层层签名，直到最终的用户证书。而Root CA证书
内置于操作系统中，所以，任何经过CA认证的数字证书都可以对其本身进行校验，确保证书本身不是伪造的。

CA，即电子商务认证中心，可以理解为一个权威的第三方机构。假设一种情景，如果两人交互，在彼此不认识的情况下，又无法识别对方的证件真伪时，一般是很难
互相取得信任的。但是，如果存在双方都信任的第三个人（中间人），这个事情就好办多了。CA就类似于这个中间人。CA会对双方的证书进行签名，这样在一方与
另一方通信时，只要携带被共同信任的CA签名过的证书，就可以认为这方可以信任。

CSR，即证书签名请求，包含自身的公钥、主体信息及与公钥对应的私钥自签名。一方可向CA机构提出证书签名请求，从而获取经CA签名过的数字证书。

我们在上网时常用的HTTPS协议就是数字证书的应用。浏览器会自动验证证书的有效性：

certificate

要使用数字证书，首先需要创建证书。正常情况下，一个合法的数字证书需要经过CA签名，这需要认证域名并支付一定的费用。开发的时候，我们可以使用自签名的
证书，这种证书可以正常开发调试，但不能对外作为服务使用，因为其他客户端并不认可未经CA签名的证书。

注：腾讯云可申请有效期1年的免费SSL证书，Let's Encrypt可申请有效期90天的免费SSL证书。

一个自签名的步骤可按照如下顺序进行：
（1）生成自签名根CA证书（含证书及私钥）
（2）生成待签名主体的密钥对（含公钥及私钥）
（3）生成待签名主体的证书签名请求（CSR）
（4）解析CSR，并根据自签名根CA证书和其对应的私钥，对待签名主体进行签名

【生成自签名根CA证书】
    这个步骤可以使用openssl进行，如在安装有openssl的电脑上，用命令行工具执行以下脚本：

  openssl ecparam -genkey -name secp256r1 | openssl ec -out privateKeys\oemRootCA.key 
					-aes128 -passout file:passphrase.txt
  openssl req -new -x509 -days 36500 -sha256 -key privateKeys\oemRootCA.key 
            -set_serial 05 -passin file:passphrase.txt 
            -config configs\oemRootCACert.cnf -extensions ext -out certs\oemRootCA.pem

	第一句使用ecparam，设置ec（用椭圆曲线算法生成密钥）时使用的曲线为secp256r1，接下来，使用ec命令，生成私钥。并使用128位AES对这个私
钥的内容加密。
	第二句提出证书签名请求，并使用SHA256算法进行签名，生成证书为X.509格式的。
经过这命令后，可生成根CA证书及私钥文件。


在Java程序中，数字证书存储在一种Java专用的key store文件中，JDK提供了一系列命令来创建和管理key store。我们用下面的命令创建一个key store，并设定口令123456：

keytool -storepass 123456 -genkeypair -keyalg RSA -keysize 1024 -sigalg SHA1withRSA -validity 3650 -alias mycert -keystore my.keystore -dname "CN=www.sample.com, OU=sample, O=sample, L=BJ, ST=BJ, C=CN"
几个主要的参数是：

keyalg：指定RSA加密算法；
sigalg：指定SHA1withRSA签名算法；
validity：指定证书有效期3650天；
alias：指定证书在程序中引用的名称；
dname：最重要的CN=www.sample.com指定了Common Name，如果证书用在HTTPS中，这个名称必须与域名完全一致。
执行上述命令，JDK会在当前目录创建一个my.keystore文件，并存储创建成功的一个私钥和一个证书，它的别名是mycert。

c:\User\Administrator>keytool -help
密钥和证书管理工具

命令:

 -certreq            生成证书请求
 -changealias        更改条目的别名
 -delete             删除条目
 -exportcert         导出证书
 -genkeypair         生成密钥对
 -genseckey          生成密钥
 -gencert            根据证书请求生成证书
 -importcert         导入证书或证书链
 -importpass         导入口令
 -importkeystore     从其他密钥库导入一个或所有条目
 -keypasswd          更改条目的密钥口令
 -list               列出密钥库中的条目
 -printcert          打印证书内容
 -printcertreq       打印证书请求的内容
 -printcrl           打印 CRL 文件的内容
 -storepasswd        更改密钥库的存储口令
 -showinfo           显示安全相关信息
 -version            输出程序版本

使用 "keytool -?, -h, or --help" 可输出此帮助消息
使用 "keytool -command_name --help" 可获取 command_name 的用法。
使用 -conf <url> 选项可指定预配置的选项文件。

有了key store存储的证书，我们就可以通过数字证书进行加解密和签名：
*/
import java.io.InputStream;
import java.math.BigInteger;
import java.security.*;
import java.security.cert.*;
import javax.crypto.Cipher;

public class 数字证书 {
    public static void main(String[] args) throws Exception {
        byte[] message = "Hello, use X.509 cert!".getBytes("UTF-8");
        // 读取KeyStore:
        KeyStore ks = loadKeyStore("/my.keystore", "123456");
        // 读取私钥:
        PrivateKey privateKey = (PrivateKey) ks.getKey("mycert", "123456".toCharArray());
        // 读取证书:
        X509Certificate certificate = (X509Certificate) ks.getCertificate("mycert");
        // 加密:
        byte[] encrypted = encrypt(certificate, message);
        System.out.println(String.format("encrypted: %x", new BigInteger(1, encrypted)));
        // 解密:
        byte[] decrypted = decrypt(privateKey, encrypted);
        System.out.println("decrypted: " + new String(decrypted, "UTF-8"));
        // 签名:
        byte[] sign = sign(privateKey, certificate, message);
        System.out.println(String.format("signature: %x", new BigInteger(1, sign)));
        // 验证签名:
        boolean verified = verify(certificate, message, sign);
        System.out.println("verify: " + verified);
    }

    static KeyStore loadKeyStore(String keyStoreFile, String password) {
        try (InputStream input = 数字证书.class.getResourceAsStream(keyStoreFile)) {
            if (input == null) {
                throw new RuntimeException("file not found in classpath: " + keyStoreFile);
            }
            KeyStore ks = KeyStore.getInstance(KeyStore.getDefaultType());
            ks.load(input, password.toCharArray());
            return ks;
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    static byte[] encrypt(X509Certificate certificate, byte[] message) throws GeneralSecurityException {
        Cipher cipher = Cipher.getInstance(certificate.getPublicKey().getAlgorithm());
        cipher.init(Cipher.ENCRYPT_MODE, certificate.getPublicKey());
        return cipher.doFinal(message);
    }

    static byte[] decrypt(PrivateKey privateKey, byte[] data) throws GeneralSecurityException {
        Cipher cipher = Cipher.getInstance(privateKey.getAlgorithm());
        cipher.init(Cipher.DECRYPT_MODE, privateKey);
        return cipher.doFinal(data);
    }

    static byte[] sign(PrivateKey privateKey, X509Certificate certificate, byte[] message)
            throws GeneralSecurityException {
        Signature signature = Signature.getInstance(certificate.getSigAlgName());
        signature.initSign(privateKey);
        signature.update(message);
        return signature.sign();
    }

    static boolean verify(X509Certificate certificate, byte[] message, byte[] sig) throws GeneralSecurityException {
        Signature signature = Signature.getInstance(certificate.getSigAlgName());
        signature.initVerify(certificate);
        signature.update(message);
        return signature.verify(sig);
    }
}
/*
在上述代码中，我们从key store直接读取了私钥-公钥对，私钥以PrivateKey实例表示，公钥以X509Certificate表示，实际上数字证书只包含公钥，因此，读取证书并不
需要口令，只有读取私钥才需要。如果部署到Web服务器上，例如Nginx，需要把私钥导出为Private Key格式，把证书导出为X509Certificate格式。

以HTTPS协议为例，浏览器和服务器建立安全连接的步骤如下：

-浏览器向服务器发起请求，服务器向浏览器发送自己的数字证书；
-浏览器用操作系统内置的Root CA来验证服务器的证书是否有效，如果有效，就使用该证书加密一个随机的AES口令并发送给服务器；
-服务器用自己的私钥解密获得AES口令，并在后续通讯中使用AES加密。
上述流程只是一种最常见的单向验证。如果服务器还要验证客户端，那么客户端也需要把自己的证书发送给服务器验证，这种场景常见于网银等。

注意：数字证书存储的是公钥，以及相关的证书链和算法信息。私钥必须严格保密，如果数字证书对应的私钥泄漏，就会造成严重的安全威胁。如果CA证书的私钥泄漏，那么该CA证书签
发的所有证书将不可信。数字证书服务商DigiNotar就发生过私钥泄漏导致公司破产的事故。
*/